package com.example.demo;

import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.TimeUnit;

public class ThreadLocalSample {
  public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    var t = new Thread() {
      @Override
      public void run() {
        var tl = ThreadLocal.withInitial(Model::new);

        tl.get();
//    tl.remove();
        tl = null;
      }

      @Override
      protected void finalize() throws Throwable {
        System.out.println("thread finalize");
      }
    };

    t.run();
//    t.join();
    t = null;


    System.gc();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("after gc");

    var tlm = getField(Thread.currentThread(), "threadLocals");
    call(tlm, "expungeStaleEntries");


    System.gc();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("after gc2");

    Object[] table = (Object[]) getField(tlm, "table");
    for (var e : table) {
      System.out.println(e);
    }
  }

  static class Model {
    public Model() {
      System.out.println("constructor");
    }

    @Override
    protected void finalize() throws Throwable {
      System.out.println("finalize");
    }
  }

  public static Object getField(Object obj, String propertyName) throws NoSuchFieldException, IllegalAccessException {
    var field = obj.getClass().getDeclaredField(propertyName);
    field.setAccessible(true);
    return field.get(obj);
  }

  public static Object call(Object obj, String methodName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    var method = obj.getClass().getDeclaredMethod(methodName);
    method.setAccessible(true);
    return method.invoke(obj);
  }
}
